package com.zzyl.nursing.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zzyl.common.constant.CacheConstants;
import com.zzyl.common.exception.base.BaseException;
import com.zzyl.common.utils.StringUtils;
import com.zzyl.nursing.domain.AlertData;
import com.zzyl.nursing.domain.AlertRule;
import com.zzyl.nursing.domain.DeviceData;
import com.zzyl.nursing.mapper.AlertRuleMapper;
import com.zzyl.nursing.mapper.DeviceMapper;
import com.zzyl.nursing.service.IAlertDataService;
import com.zzyl.nursing.service.IAlertRuleService;
import com.zzyl.system.mapper.SysUserRoleMapper;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 报警规则Service业务层处理
 * 
 * @author Nuyoah
 * @date 2025-05-29
 */
@Slf4j
@Service
public class AlertRuleServiceImpl extends ServiceImpl<AlertRuleMapper, AlertRule> implements IAlertRuleService
{
    @Autowired
    private AlertRuleMapper alertRuleMapper;

    /**
     * 查询报警规则
     * 
     * @param id 报警规则主键
     * @return 报警规则
     */
    @Override
    public AlertRule selectAlertRuleById(Long id)
    {
        return getById(id);
    }

    /**
     * 查询报警规则列表
     * 
     * @param alertRule 报警规则
     * @return 报警规则
     */
    @Override
    public List<AlertRule> selectAlertRuleList(AlertRule alertRule)
    {
        return alertRuleMapper.selectAlertRuleList(alertRule);
    }

    /**
     * 新增报警规则
     * 
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int insertAlertRule(AlertRule alertRule)
    {
        return save(alertRule) ? 1 : 0;
    }

    /**
     * 修改报警规则
     * 
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int updateAlertRule(AlertRule alertRule)
    {
        return updateById(alertRule) ? 1 : 0;
    }

    /**
     * 批量删除报警规则
     * 
     * @param ids 需要删除的报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleByIds(Long[] ids)
    {
        return removeByIds(Arrays.asList(ids)) ? 1 : 0;
    }

    /**
     * 删除报警规则信息
     * 
     * @param id 报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleById(Long id)
    {
        return removeById(id) ? 1 : 0;
    }

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private DeviceMapper deviceMapper;

    @Autowired
    private SysUserRoleMapper sysUserRoleMapper;

    @Autowired
    private IAlertDataService alertDataService;

    @Value("${alertRule.roleAdmin}")
    private String roleAdmin;


    @Value("${alertRule.roleRepair}")
    private String roleRepair;
    /**
     * 设备数据报警规则校验定时任务
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void alertRuleFilter() {
        //1.查询所有规则
        Long ruleCount = this.lambdaQuery().eq(AlertRule::getStatus, 1).count();
        if (ruleCount<=0){
            log.error("[定时报警规则校验]没有任何报警规则，无需处理");
            return;
        }

        //2.查询所有设备上报的数据
        List<Object> values = redisTemplate.opsForHash().values(CacheConstants.IOT_DEVICE_LIST_DATA_KEY);
        if (CollUtil.isEmpty(values)){
            log.error("[定时报警规则校验]没有任何设备数据，无需处理");
            return;
        }

        //3.对数据格式进行标准化处理
        List<DeviceData> deviceDataAllList = new ArrayList<>();
        values.forEach(v->{
            List<DeviceData> deviceData = JSONUtil.toList((String) v, DeviceData.class);
            deviceDataAllList.addAll(deviceData);
        });

        //4.获取全部数据,进行过滤处理
        for (DeviceData deviceData : deviceDataAllList) {
            //4.1校验设备上报时间是否太久(由业务规则决定,可变)
            int limit = 600;//单位秒，超过十分钟的设备上报数据不再处理
            long between = LocalDateTimeUtil.between(deviceData.getAlarmTime(), LocalDateTime.now(), ChronoUnit.SECONDS);
            if (between>limit){
                log.error("[定时报警规则校验]数据上报时间太久,超过十分钟的设备上报数据不再处理");
                return;
            }

            /**
             * 4.2查询上报数据对应的设备绑定的全部报警规则数据
             */
            List<AlertRule> allAlertRuleList = getAlertRules(deviceData);

            if (allAlertRuleList == null) {
                continue;
            }
            for (AlertRule alertRule : allAlertRuleList) {
                //第一层校验,判断是否在生效时间内,生效时间格式:00:00:00~23:59:59
                if (checkVaildTime(deviceData, alertRule)) {
                    continue;
                }

                //第二层校验,检验数据是否达到阈值
                Double dataValue = Double.valueOf(deviceData.getDataValue());//数据上报的值
                Double value = alertRule.getValue();//阈值
                String operator = alertRule.getOperator();//比较运算符 >= 或 <

                //x>y,返回1 x<y,返回-1 x=y,返回0
                int compare = NumberUtil.compare(dataValue, value);

                //达到阈值
                if (">=".equals(operator)&&compare>=0||"<".equals(operator)&&compare<0){
                    //第三层校验,判断沉默周期是否存在
                    String silentKey  = CharSequenceUtil.format(CacheConstants.IOT_ALERT_SILENT_PREFIX, deviceData.getIotId(), deviceData.getFunctionId(), alertRule.getId());
                    String silentVal = redisTemplate.opsForValue().get(silentKey);
                    if (StringUtils.isNotBlank(silentVal)){
                        log.error("[定时报警规则校验]当前上报数据还在沉默周期内，无需处理");
                        continue;
                    }
                    //第四层校验,从redis中查询已报警次数,判断报警次数是否等于持续周期数
                    String countKey = CharSequenceUtil.format(CacheConstants.IOT_ALERT_COUNT_PREFIX, deviceData.getIotId(), deviceData.getFunctionId(), alertRule.getId());
                    Long increment = redisTemplate.opsForValue().increment(countKey);
                    if (increment<alertRule.getDuration()){
                        log.error("[定时报警规则校验]当前上报数据还未达到持续周期次数，无需处理");
                        continue;
                    }
                    //校验全部通过,当前数据触发报警

                    //保存静默周期到redis中
                    redisTemplate.opsForValue().setIfAbsent(silentKey, "silentKey", alertRule.getAlertSilentPeriod()*60, TimeUnit.SECONDS);

                    //删除redis报警数
                    redisTemplate.delete(countKey);

                    List<Long> nursingIds = null;

                    
                    /**
                     * 判断是老人异常数据还是设备异常数据 alert_data_type = 0 老人异常;  =1 设备异常
                     */
                    ArrayList<Long> nursingIdList = nursingIds(deviceData, alertRule);


                    /**
                     * 封装并批量保存报警数据
                     */
                    saveBatchAlertData(deviceData, alertRule, nursingIdList);


                }else {
                    log.error("[定时报警规则校验]当前上报数据没有达到阈值，无需处理");
                    continue;
                }


            }
        }

    }

    @Nullable
    private List<AlertRule> getAlertRules(DeviceData deviceData) {
        //4.2.1 查询当前上报数据对应的产品类型下同样的功能绑定的全部规则数据(针对某种规则既绑定了全部设备,又有另一种规则绑定了这批设备中的某一个设备的情况)
        List<AlertRule> productAlertRuleList  = this.lambdaQuery()
                .eq(AlertRule::getProductKey, deviceData.getProductKey())
                .eq(AlertRule::getFunctionId, deviceData.getFunctionId())
                .eq(AlertRule::getIotId, -1)
                .eq(AlertRule::getStatus, 1)
                .list();
        if (CollUtil.isEmpty(productAlertRuleList)){
            productAlertRuleList = new ArrayList<>();
        }
        //4.2.2查询当前上报数据对应的设备下绑定的全部规则数据
        List<AlertRule> deviceAlertRuleList = this.lambdaQuery()
                .eq(AlertRule::getProductKey, deviceData.getProductKey())
                .eq(AlertRule::getFunctionId, deviceData.getFunctionId())
                .eq(AlertRule::getIotId, deviceData.getIotId())
                .eq(AlertRule::getStatus, 1)
                .list();
        if (CollUtil.isEmpty(deviceAlertRuleList)){
            deviceAlertRuleList = new ArrayList<>();
        }

        //4.2.3合并所有报警规则
        productAlertRuleList.addAll(deviceAlertRuleList);
        //4.2.4判断报警规则是否为空
        if(CollUtil.isEmpty(productAlertRuleList)){
            log.error("[定时报警规则校验]当前上报数据对应的设备没匹配到任何报警规则，无需处理");
            return null;
        }
        //4.2.5对报警规则进行去重处理
        List<AlertRule> allAlertRuleList = CollUtil.distinct(productAlertRuleList);
        return allAlertRuleList;
    }

    private static boolean checkVaildTime(DeviceData deviceData, AlertRule alertRule) {
        String alertEffectivePeriod = alertRule.getAlertEffectivePeriod();
        String[] split = alertEffectivePeriod.split("~");
        LocalTime startTime = LocalTime.parse(split[0]);//生效开始时间
        LocalTime endTime = LocalTime.parse(split[1]);//生效结束时间
        LocalTime alarmTime = deviceData.getAlarmTime().toLocalTime();//报警时间
        if (alarmTime.isBefore(startTime)||alarmTime.isAfter(endTime)){
            log.error("[定时报警规则校验]当前上报数据不在生效时间内，无需处理");
            return true;
        }
        return false;
    }

    /**
     * 判断是老人异常数据还是设备异常数据 alert_data_type = 0 老人异常;  =1 设备异常
     * @param deviceData
     * @param alertRule
     * @return
     */
    @NotNull
    private ArrayList<Long> nursingIds(DeviceData deviceData, AlertRule alertRule) {
        List<Long> nursingIds;
        if (alertRule.getAlertDataType()==0){
            if (deviceData.getLocationType()==0){
                //如果是移动设备,则直接查到老人对应的护理人员
                nursingIds = deviceMapper.selectNursingIdsByMoveIotId(deviceData.getIotId());

            }else {
                //如果是固定设备,则需要关联床位后再查老人对应的护理人员
                nursingIds=deviceMapper.selectNursingIdsByStopIotId(deviceData.getIotId());
            }
        }else {
            //设备异常,直接调用角色名获取用户ID,即查询维修工
            nursingIds=sysUserRoleMapper.selectUserIdsByRoleName(roleRepair);
        }

        //查询超级管理员
        List<Long> adminUserIds = sysUserRoleMapper.selectUserIdsByRoleName(roleAdmin);

        //合并负责人ID
        nursingIds.addAll(adminUserIds);
        //去重
        ArrayList<Long> nursingIdList = CollUtil.distinct(nursingIds);
        return nursingIdList;
    }

    /**
     * 封装并批量保存报警数据
     * @param deviceData
     * @param alertRule
     * @param nursingIdList
     */
    private void saveBatchAlertData(DeviceData deviceData, AlertRule alertRule, ArrayList<Long> nursingIdList) {
        //封装报警的基础数据对象
        AlertData alertData = new AlertData();
        BeanUtils.copyProperties(deviceData,alertData);
        alertData.setId(null);//设置ID为空,否则批量新增时会出现ID重复异常,导致数据无法添加成功
        alertData.setType(alertRule.getAlertDataType());//设备数据类型 0-老人异常数据 1-设备异常数据
        alertData.setAlertRuleId(alertRule.getId());//规则id
        alertData.setStatus(0);//0-待处理  1-已处理
        String reasonTemplate = "功能{}{}{}，持续了{}个周期才报警，沉默周期：{}";
        String alertReason = CharSequenceUtil.format(reasonTemplate,
                alertRule.getFunctionId(),
                alertRule.getOperator(),
                alertRule.getIotId(),
                alertRule.getDuration(),
                alertRule.getAlertSilentPeriod()
        );
        //设置报警原因
        alertData.setAlertReason(alertReason);
        List<AlertData> userAlertDataList = new ArrayList<>();
        //为每个负责的员工设置响应报警信息
        for (Long userId : nursingIdList) {
            AlertData userAlertData = new AlertData();
            BeanUtils.copyProperties(alertData,userAlertData);
            userAlertData.setUserId(userId);
            userAlertDataList.add(userAlertData);
        }

        //批量保存
        boolean result = alertDataService.saveBatch(userAlertDataList);
        if(!result){
            throw new BaseException("批量保存数据失败");
        }
    }
}
